using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Meta;

namespace Meta.Editor {
	/// <summary>
	/// A code editor window with a read-eval-print loop attached to it.
	/// </summary>
	
	public class Listener : Editor, Meta.IGUIInterface {
		static public bool focusOnWrite=false;

		LogInspector logWindow=null;
		bool LogWindowOpen {
			get {
				return logWindow!=null && !logWindow.IsDisposed;
			}
		}

		void MakeLogWindow() {
			if (this.InvokeRequired)
				this.Invoke(new VoidThunk(ReallyMakeLogWindow), new object[0]);
			else ReallyMakeLogWindow();
		}

		void ReallyMakeLogWindow() {
			// Make sure there wasn't a race condition whereby two requests to make the window got through.
			if (!LogWindowOpen) {
				logWindow = new LogInspector();
				logWindow.Show();
			}
		}

		LogInspector LogWindow {
			get {
				if (!LogWindowOpen)
					MakeLogWindow();
				return logWindow;
			}
		}

#if OldMain
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		/// 
		[STAThread]
		static void Main() {
			Listener listener = new Listener();
			mainListener = listener;
			// The Meta.TopLevel class is now loaded and initialized.

			//This is ThreadStatic, so setting it only blocks package loading from occurring in the GUI thread.
			Meta.Package.inhibitLoading = true;

#if BUILDIN2D
			// This really shouldn't be wired into the IDE, but I haven't figured out how to get Dev Studio to
			// compile it into the Packages directory.
			Namespace basic2D = Namespace.MakeStandardNamespace(Symbol.Intern("Basic2D"));
			Fluid.Bind(new object[] { Namespace.currentNamespaceFluid, basic2D }, new Meta.Thunk(LoadBasic2D));
			Namespace.RootNamespace.Define("Basic2D", basic2D, "Basic 2D graphics package");
			Namespace.UserNamespace.Import(basic2D);
#endif

			//Namespace.DefineManifestConstant("Editor", typeof(Editor), "The Meta code editor");
			//Namespace.DefineManifestConstant("Listener", typeof(Listener), "The Listener window class");
			Assembly me = Assembly.GetAssembly(typeof(Meta.TopLevel));
			AssemblyCopyrightAttribute copyright = (AssemblyCopyrightAttribute)(me.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)[0]);
			Version version = me.GetName().Version;

			listener.Out.WriteLine("Meta development environment, version {0}.{1}   (revision {2} build {3})", new object[] {version.Major, version.Minor, version.Revision, version.Build});
			listener.Out.WriteLine("Copyright \u00a9 {0}.\n", copyright.Copyright);

			// Force the jitter to compile the editor now so we don't have any glitches while the user types their first expression.
			listener.ReindentAllMenuItem_Click(null, null);
			listener.SetArglistText("Welcome to Meta");

			listener.Out.Write(Meta.TopLevel.promptString);
			listener.Out.Flush();
			Thread.CurrentThread.Name = "GUI Thread";
			Meta.TopLevel.idle += new Meta.VoidThunk(ResetCursors);
			Meta.TopLevel.evalStart += new VoidThunk(((IGUIInterface)listener).ResetLog);
			listener.ShowNamespace();

			ProcessCommandLineArgs();

			System.AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(Meta.TopLevel.UnhandledExceptionHandler);
			System.Windows.Forms.Application.Run(listener);
		}
#endif

#if BUILDIN2D
		static object LoadBasic2D() {
			New2D.GraphicObjects.Initialize();
			return null;
		}
#endif

#if OldScrollCode
	const int SB_VERT = 1; 
    const int EM_SETSCROLLPOS = 0x0400 + 222; 
  
     [DllImport("user32", CharSet=CharSet.Auto)] 
     public static extern bool GetScrollRange(IntPtr hWnd, int nBar, out int lpMinPos, out int lpMaxPos); 
 
     [DllImport("user32", CharSet=CharSet.Auto)] 
     public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, POINT lParam); 
 
     [StructLayout(LayoutKind.Sequential)] 
     public class POINT 
     { 
          public int x; 
          public int y; 
 
          public POINT() 
          { 
          } 
 
          public POINT(int x, int y) 
 
          { 
               this.x = x; 
               this.y = y; 
          } 
     } 
#endif
 		const int WM_VSCROLL = 0x115;
		const int SB_BOTTOM = 7;
		[DllImport("user32", CharSet=CharSet.Auto)] 
		public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int param); 
 
        // Scroll the RTB so the bottom of the text is always visible. (Cribbed from WINDOWS FORMS FAQ)
		void ForceScrollToEnd() {
#if OldScrollCode
			int min, max; 
 
			GetScrollRange(code.Handle, SB_VERT, out min, out max); 
			SendMessage(code.Handle, EM_SETSCROLLPOS, 0, new POINT(0, max-code.Height+30)); 
#endif
			SendMessage(code.Handle, WM_VSCROLL, SB_BOTTOM, 0);
		}

		public static void ResetCursors() {
			mainListener.Invoke(new IHateDefiningTheseToo(SetAllCursorsInternal), new object[1] { System.Windows.Forms.Cursors.IBeam });
		}

        delegate void IHateDefiningTheseToo(Cursor x);

		object ReallyMakeOutputWindow() {
			OutputWindow w = new OutputWindow();
			w.Show();
			return w;
		}

		Meta.OutputWindow Meta.IGUIInterface.MakeOutputWindow() {
			return (Meta.OutputWindow)(mainListener.Invoke(new Meta.Thunk(ReallyMakeOutputWindow), new object[0]));
		}

		void Meta.IGUIInterface.Log(object o) {
			BeginInvoke(new VerilyIHateDefiningThese(this.ReallyLog), new object[] {o});
		}

		void Meta.IGUIInterface.RefreshLog() {
			BeginInvoke(new Meta.VoidThunk(LogWindow.ClearRenderCache), new object[0]);
		}

		void ReallyLog(object o) {
			LogWindow.Log(o);
		}

		void Meta.IGUIInterface.ResetLog() {
			Invoke(new Meta.VoidThunk(ReallyResetLog), new object[0]);
		}

		void ReallyResetLog() {
			if (LogWindowOpen)
				LogWindow.ClearLog(null, null);
		}

		delegate void VerilyIHateDefiningThese(object o);

		object Meta.IGUIInterface.Invoke(System.Delegate proc, Object[] args) {
			return Invoke(proc, args);
		}

		void Meta.IGUIInterface.SetStatusMessage(string message) {
			Listener.mainListener.SetArglistText(message);
		}

		delegate void IHateDefiningThese(Meta.Expression e, bool localize, bool raise);
		void Meta.IGUIInterface.EditSource(Meta.Expression e, bool localizeToExpression, bool raise) {
			((IGUIInterface)this).Invoke(new IHateDefiningThese(Editor.EditSource), new object[] { e, localizeToExpression, raise } );
		}

		public static Listener mainListener;

		public const string ListenerTitle = "Interaction Window";
		public Listener() : base () {
			Name = ListenerTitle;
			Text = ListenerTitle;
			Out = new ListenerTextWriter(code, this);
			interpreter = new Meta.TopLevel(Out, this);
			Show();
			GotoMenuItem.Enabled = false;
		}

		//protected override void OnVisibleChanged(EventArgs e) {
		//	MaybeProcessCommandLineArgs();
		//	base.OnVisibleChanged (e);
		//}

		public static void ProcessCommandLineArgs() {
			string[] arglist = System.Environment.GetCommandLineArgs();
			for (int i=1;i<arglist.Length; i++)
				ProcessCommandLineArg(arglist[i]);
		}

		public static void ProcessCommandLineArgs(string[] arglist) {
			for (int i=0;i<arglist.Length; i++)
				ProcessCommandLineArg(arglist[i]);
		}

        public static void ProcessCommandLine(System.Collections.ObjectModel.ReadOnlyCollection<string> commandline)
        {
            foreach (string arg in commandline)
                ProcessCommandLineArg(arg);
        }

        [DllImport("user32.dll")]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        public static void ProcessCommandLineArg(string arg)
        {
			if (arg.EndsWith(".mdump"))
				Editor.LoadDumpFile(arg);
            else if (arg.EndsWith(".mscript"))
            {
                // Note: this will probably fail if there are two scripts.
                Meta.Reader reader = new Reader(new RTFTextReader(new StreamReader(arg)), 0);
                mainListener.LoadStream(reader, FileToken.GetToken(arg));
                // Stream will be closed by LoadStream...
            }
            else
            {
                Editor ed = FindEditor(FileToken.GetToken(arg));
                if (ed.WindowState == FormWindowState.Minimized)
                    ed.WindowState = FormWindowState.Normal;
                // Cribbed from http://en.csharp-online.net/Application_Architecture_in_Windows_Forms_2.0%E2%80%94Single-MDI_Applications
                // because Activate alone only brings it to the front of Meta's windows; it won't seak focus from the shell.
                //SetForegroundWindow(ed.Handle);
                ed.Activate();
            }
		}

		public void WriteInternal(System.Drawing.Image i) {
			code.InsertImage(i);
#if INSERTIMAGEUSINGCLIPBOARD
			code.undoEnabled = false;  // kluge - undo strings get too big.
			Clipboard.SetDataObject(i);
			//IDataObject foo = Clipboard.GetDataObject();
			//string[] formats = foo.GetFormats();
			if (i is System.Drawing.Bitmap)
				code.Paste(DataFormats.GetFormat(DataFormats.Bitmap));
			else if (i is System.Drawing.Imaging.Metafile)
				code.Paste(DataFormats.GetFormat(DataFormats.EnhancedMetafile));
			else
				code.AppendText(i.ToString());
			//code.ScrollToCaret();
			ForceScrollToEnd();
			if (focusOnWrite)
				Focus();
			//buffer.SelectionIndent=0;
			code.ResetCommandPasting();
#endif
		}

		public void WriteInternal(string s) {
			if (s.Length>=2 && s[s.Length-2] == Meta.TopLevel.promptCarret) {
				// Attempt to pretty-print the buffer after the fact (ick)
				code.LockWindow();
				code.AppendText(s);
				code.PrettyPrintKluge();
				code.UnlockWindow();
			} else
				code.AppendText(s);
			//code.ScrollToCaret();
			ForceScrollToEnd();
			if (focusOnWrite)
				Focus();
			code.ResetCommandPasting();
		}

		private void UnindentEndOfBufferInternal() {
			code.SelectionLength=0;
			code.SelectionStart=code.TextLength;
			code.SelectionIndent=0;
			code.NoteUserSelection(); //paranoia
			code.ResetCommandPasting();
		}

		public class ListenerTextWriter : Meta.SomewhatRichTextWriter {
			public ListenerTextWriter(CodeBox buf, Listener win) {
				buffer = buf;
				window = win;
			}
			CodeBox buffer;
			Listener window;
			const int bufferSize = 50;

			char[] outputBuffer= new char[bufferSize+1];
			int bufferIndex=0;  // next position in buffer to be filled.

			public override void Flush() {
				if (bufferIndex>0)
					window.Invoke(new IShouldntHaveToDeclareThisEither(window.WriteInternal), new object[1] { new string(outputBuffer, 0, bufferIndex) });
				bufferIndex=0;
			}
		
			private delegate void IShouldntHaveToDeclareThis(System.Drawing.Image i);
			public override void Write(System.Drawing.Image i) {
				Flush();
				window.Invoke(new IShouldntHaveToDeclareThis(window.WriteInternal), new object[1] {i});
			}
			private delegate void IShouldntHaveToDeclareThisEither(string s);

			//
			// Horrible kluge.
			// This is how we keep from inserting newline twice when doing control-enter in the listener.
			//
			public override void Write(string s) {
				foreach (char c in s) {
					outputBuffer[bufferIndex++] = c;
					if (bufferIndex>bufferSize)
						Flush();
				}
				//Flush();
				//window.Invoke(new IShouldntHaveToDeclareThisEither(window.WriteInternal), new object[1] {s});
			}

			public override void UnindentEndOfBuffer() {
				Flush();
				window.Invoke(new Thunk(window.UnindentEndOfBufferInternal));
			}

			public override System.Drawing.Color GetBackColor() {
				return window.code.BackColor;
			}

			bool suppressNewLine=false;
			public void SuppressNewLine() {
				suppressNewLine=true;
			}

			public override void WriteStringInFont(string s, System.Drawing.Font f) {
				Write(s);
			}

			public override void Write(char c) {
				if (c=='\n' && suppressNewLine) {
					suppressNewLine = false;
					return;
				}
				if (c=='\r')
					return;
				outputBuffer[bufferIndex++] = c;
				if (bufferIndex>bufferSize)
					Flush();
			}
			public override Encoding Encoding {
				get {
					return Encoding.Default;
				}
			}
			public override System.Drawing.Graphics CreateGraphics() {
				return window.CreateGraphics();
			}
		}

		public ListenerTextWriter Out;
		public Meta.TopLevel interpreter;
		public Thread evalThread;

		public static bool IsRunning {
			get {
				Thread evalThread = mainListener.evalThread;
				return evalThread != null && evalThread.IsAlive;
			}
		}
	
		class EvalStarter {
			string command;
			Listener listener;
			bool stepMode;
			bool inspectMode;
			FileToken sourceFileName;
			int filePosition;
			bool print;

			public EvalStarter(string c, Listener l, bool stepMode, bool inspectMode, FileToken sourceFileName, int filePosition, bool print) {
				command = c;
				listener = l;
				this.stepMode = stepMode;
				this.inspectMode = inspectMode;
				this.sourceFileName = sourceFileName;
				this.filePosition = filePosition;
				this.print = print;
			}
			public void Run() {
				Meta.TopLevel.InstallFormDebugger();
				Namespace n = FileNamespace(sourceFileName);
				Fluid.Bind(new object[] { Meta.TopLevel.m_currentSourceFileToken, sourceFileName, Meta.TopLevel.m_currentSourceDefinition, null,
													Reader.currentReader, null,
				                                    Meta.Namespace.currentNamespaceFluid, n==null?Namespace.CurrentNamespace:n},
							    new Meta.Thunk(Run2));
			}
			public object Run2() {
				Namespace ns = Namespace.CurrentNamespace;
				listener.SetAllCursors(Cursors.WaitCursor);
				double elapsedSeconds = Meta.TopLevel.CurrentThreadTopLevel.EvalString(command, stepMode, inspectMode, filePosition, print);
				if (print && elapsedSeconds>0.015)
					// Clock never reads less than 0.010044 seconds
					listener.SetValueText(elapsedSeconds.ToString()+" seconds");
//				listener.SetAllCursors(Cursors.IBeam);
				Meta.TopLevel.CurrentSourceFileToken = null;
				return null;
			}
		}

		public static ArrayList previousCommands = new ArrayList();
		public static ArrayList previousCommandLengths = new ArrayList();
		public void EvalString(string command, bool stepMode, bool inspectMode, FileToken sourceFileName, int filePosition, bool print) {
			if (command=="")
				MessageBox.Show("There is no expression to execute.");
			else if (IsRunning)
				MessageBox.Show("The system is already running a previous command.", "Please wait", MessageBoxButtons.OK);
			else {
				// previousCommands.Push(command);  Can't put it here because we want the Rtf.  Oh well.  It gets pushed in 
				evalThread = new Thread(new ThreadStart(new EvalStarter(command, this, stepMode, inspectMode, sourceFileName, filePosition, print).Run));
				evalThread.Name = "Eval thread";
				evalThread.Start();
			}
		}

		class LoadStarter {
			Listener listener;
			Meta.Reader reader;
			FileToken filename;

			public LoadStarter(Listener listener, Meta.Reader reader, FileToken filename) {
				this.listener = listener;
				this.reader = reader;
				this.filename = filename;
			}

			public void Run() {
				Namespace n = FileNamespace(filename);
				Fluid.Bind( new object[] { Meta.TopLevel.m_currentSourceFileToken, filename, Reader.currentReader, reader,
														Meta.TopLevel.m_currentSourceDefinition, null,
														Meta.Namespace.currentNamespaceFluid, n==null?Namespace.CurrentNamespace:n}, new Meta.Thunk(RunInternal));
			}

			public object RunInternal() {
				listener.SetAllCursors(Cursors.WaitCursor);
				long startTicks = Process.GetCurrentProcess().UserProcessorTime.Ticks;
				try {
					Meta.TopLevel.CurrentThreadTopLevel.LoadStreamWithErrorTrapping(reader, new object[2] { String.Intern("load"), filename });
				} finally {
					reader.Close();
				}
				long elapsedTicks = Process.GetCurrentProcess().UserProcessorTime.Ticks - startTicks;
				double elapsedSeconds = ((double)elapsedTicks)/System.TimeSpan.TicksPerSecond;
				listener.SetValueText(elapsedSeconds.ToString()+" seconds");
//				listener.SetAllCursors(Cursors.IBeam);

				return null;
			}
		}

		public void LoadStream(Meta.Reader reader, FileToken filename) {
			if (IsRunning)
				MessageBox.Show("The system is already running a previous command.", "Please wait", MessageBoxButtons.OK);
			else {
				evalThread = new Thread(new ThreadStart(new LoadStarter(this, reader,filename).Run));
				evalThread.Name = "Eval thread";
				evalThread.Start();
			}
		}

		public void KillEvalThread() {
			if (Meta.TopLevel.ThreadBeingDebugged!=null)
				Meta.TopLevel.ThreadBeingDebugged.Kill();
			else if (IsRunning)
				evalThread.Abort();

//			SetAllCursors(Cursors.IBeam);
			interpreter.Prompt();
		}
	}
}

